Python3中的C3算法:多继承查找规则

标签(空格分隔): Python3探索


一、基本概念

1. mro序列

  • MRO是一个有序列表L,在类被创建时就计算出来。

  • 通用计算公式为:

     mro(Child(Base1,Base2)) = [ Child ] + merge( mro(Base1), mro(Base2),  [ Base1, Base2] )
     (其中Child继承自Base1, Base2)
    
  • 如果继承至一个基类:class B(A)
    这时B的mro序列为

     	mro( B ) = mro( B(A) )
     	= [B] + merge( mro(A) + [A] )
     	= [B] + merge( [A] + [A] )
     	= [B,A]
    
  • 如果继承至多个基类:class B(A1, A2, A3 …)
    这时B的mro序列

     	mro(B)  = mro( B(A1, A2, A3 …) )
     	= [B] + merge( mro(A1), mro(A2), mro(A3) ..., [A1, A2, A3] )
     	= ...
    
  • 计算结果为列表,列表中至少有一个元素即类自己,如上述示例[A1,A2,A3]。merge操作是C3算法的核心。

2. 表头和表尾:

  • 表头:
    列表的第一个元素

  • 表尾:
    列表中表头以外的元素集合(可以为空)

  • 示例
    列表:[A, B, C]
    表头是A,表尾是B和C

3. 列表之间的+操作

+操作:

[A] + [B] = [A, B]
(以下的计算中默认省略)

3. merge操作:

  • merge操作流程图:
    这里写图片描述

  • merge操作示例:

      如计算merge( [E,O], [C,E,F,O], [C] )
      有三个列表 :  ①      ②          ③
    
      1 merge不为空,取出第一个列表列表①的表头E,进行判断                              
         各个列表的表尾分别是[O], [E,F,O],E在这些表尾的集合中,因而跳过当前当前列表
      2 取出列表②的表头C,进行判断
         C不在各个列表的集合中,因而将C拿出到merge外,并从所有表头删除
         merge( [E,O], [C,E,F,O], [C]) = [C] + merge( [E,O], [E,F,O] )
      3 进行下一次新的merge操作 ......
    

二、实战测试

1. 计算实例1

示例:(多继承UML图,引用见参考)
多继承UML图:
备注:O==object

如何计算mro(A) ?

mro(A) = mro( A(B,C) )

原式= [A] + merge( mro(B),mro(C),[B,C] )

  mro(B) = mro( B(D,E) )
         = [B] + merge( mro(D), mro(E), [D,E] )  # 多继承
         = [B] + merge( [D,O] , [E,O] , [D,E] )  # 单继承mro(D(O))=[D,O]
         = [B,D] + merge( [O] , [E,O]  ,  [E] )  # 拿出并删除D
         = [B,D,E] + merge([O] ,  [O])
         = [B,D,E,O]
    
  mro(C) = mro( C(E,F) )
         = [C] + merge( mro(E), mro(F), [E,F] )
         = [C] + merge( [E,O] , [F,O] , [E,F] )
         = [C,E] + merge( [O] , [F,O]  ,  [F] )  # 跳过O,拿出并删除
         = [C,E,F] + merge([O] ,  [O])
         = [C,E,F,O]
        
原式= [A] + merge( [B,D,E,O], [C,E,F,O], [B,C])
    = [A,B] + merge( [D,E,O], [C,E,F,O],   [C])
    = [A,B,D] + merge( [E,O], [C,E,F,O],   [C])  # 跳过E
    = [A,B,D,C] + merge([E,O],  [E,F,O])
    = [A,B,D,C,E] + merge([O],    [F,O])  # 跳过O
    = [A,B,D,C,E,F] + merge([O],    [O])
    = [A,B,D,C,E,F,O]

2. 实例代码测试

对于以上计算,用代码来测试。

class D: pass
class E: pass
class F: pass
class B(D,E): pass
class C(E,F): pass
class A(B,C): pass

print("从A开始查找:")
for s in A.__mro__:
	print(s)

print("从B开始查找:")
for s in B.__mro__:
	print(s)

print("从C开始查找:")
for s in C.__mro__:
	print(s)

结果:

从A开始查找:
<class '__main__.A'>
<class '__main__.B'>
<class '__main__.D'>
<class '__main__.C'>
<class '__main__.E'>
<class '__main__.F'>
<class 'object'>
从B开始查找:
<class '__main__.B'>
<class '__main__.D'>
<class '__main__.E'>
<class 'object'>
从C开始查找:
<class '__main__.C'>
<class '__main__.E'>
<class '__main__.F'>
<class 'object'>

三、总结

每次判断如何读取都要这么麻烦计算吗?可有简单方法?
我对此做了一个简单总结。

1. 规律总结


如何快速判断查找规律?

简单的八个字总结就是:“从一至顶,有子先出”。
一句话解释就是,从当前查找类第一个待查找父类一直向上查找,当遇到父类还有其他子类时,且子类也是当前查找类的父类或父父类,则优先查找其子类,直到到达查找顶端Object类(跳过Object类),再查找下一个待查找父类。重复至结束所有查找。


查找规律文字描述:

  • 取出待查找类D:将当前查找类D放入到查找列表__mro__
  • 判断1:待查找类D的所有父类父类的父类是否还未遍历完毕?
    • 否,则取出object,放入到查找列表__mro__,【查找结束】
    • 是,准备从当前查找类继续向上查找剩余的父类,进行判断2。
      • 判断2:待查找父类是否是Object。
        • 是,重置当前查找类待查找类D,回到判断1
        • 否,进行判断3
          • 判断3:待查找父类是否包含其他子类,且子类满足待查找类D的父类或父类的父类。
            • 是,则暂时跳过该 待查找父类 的查找,将父类的下一个“满足是待查找类D的父类”的子类取出放入到查找列表__mro__
            • 否,则将待查找父类取出放入到查找列表__mro__
          • 准备从当前查找类继续向上查找它的父类。回到判断2

查找规律流程图:
在这里插入图片描述
其实整个过程可以浓缩为一个入栈出栈的过程,代码可以用递归实现。但实现的流程图并不容易绘制出来,如果您有更好的想法,请留言我们大家一起共同交流。

2. 规律测试

实例2:

对于如下继承:
这里写图片描述
通过如下判断模式:

代码测试:

class A1: pass
class A2: pass
class A3: pass
class B1(A1,A2): pass
class B2(A2): pass
class B3(A2,A3): pass
class C1(B1): pass
class C2(B1,B2): pass
class C3(B2,B3): pass
class D(C1, C2, C3): pass

print("从D开始查找:")
for s in D.__mro__:
	print(s)

print("从C3开始查找:")
for s in C3.__mro__:
	print(s)

结果参考:

  • 规律解释:
当前类最底层D的未查找的父类列表预查找的父类预查找父类是否是object预查找父类是否还有的子类? 同时又是底层查找类的父类实际取出的类
DC1, C2, C3C1--C1
C1C2, C3B1-有,C2C2
C2C3B1--B1
B1C3A1--A1
A1C3object是,但最底层D还有父类C3未取出-C3
C3-B2--B2
B2-A2-是,B3B3
B3-A2--A2
A2-object是,最底层D已经没有父类未取出了是,A3A3
A3-object-object
实例3:
r"""
    O
 /  |  \
A1  A2  A3
|   | \ |
|   |  \|
B1  B2  B3
| \ | \  
|  \|  \
C1  C2  C3
 \  |   /
    D
"""

class A1: pass
class A2: pass
class A3: pass

class B1(A1):pass
class B2(A2):pass
class B3(A2,A3):pass

class C1(B1):pass
class C2(B1, B2):pass
class C3(B2):pass

class D(C1, C2, C3):pass

print(*D.__mro__, sep='\n')

运行结果:

<class '__main__.D'>
<class '__main__.C1'>
<class '__main__.C2'>  # C2是C1父类的子类,优先查找
<class '__main__.B1'>
<class '__main__.A1'>
<class '__main__.C3'>  # D还有父类C3待查找,暂时跳过Object
<class '__main__.B2'>
<class '__main__.A2'>
<class 'object'>   # B3、A3不是D的父类,所以不加入查找列表

四、参考

python多重继承C3算法 - CSDN博客
https://blog.csdn.net/fmblzf/article/details/52512145
【Python】C3算法 - foreverlove~ - 博客园
https://www.cnblogs.com/bashaowei/p/8508276.html

  • 22
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值